const testEmptyArray=(arr)=>arr==undefined?[]:arr;

String.prototype.contains = function (str) {
    return this.indexOf(str) != -1;
};

String.prototype.escapeText = function () {
    return this.replace(/"/g,'\\"').replace(/\n/g,'\\\\n');
};

String.prototype.unescapeText = function () {
    return this.unescapeEnter().unescapeQuota();
};

String.prototype.escapeQuota = function () {
    return this.replace(/"/g,'\\"');
};

String.prototype.unescapeQuota = function () {
    return this.replace(/\\"/g,'"');
};

String.prototype.escapeEnter = function () {
    return this.replace(/\n/g,'\\\\n');
};

String.prototype.unescapeEnter = function () {
    return this.replace(/\\\\n/g,'\n');
};

function getRequest() {
    var url = location.search; //获取url中"?"符后的字串
    var theRequest = {};
    if (url.indexOf("?") != -1) {
        var str = url.substr(1);
        var strs = str.split("&");
        for(var i = 0; i < strs.length; i ++) {
            theRequest[strs[i].split("=")[0]]=decodeURI(strs[i].split("=")[1]);
        }
    }
    return theRequest;
}

function getConfigure(ftype,callback) {
    if(ftype == null){
        return;
    }

    let session = neo4jService.getSession();
    let resultPromise = session.run(`MATCH (n:Configure) WHERE n.ftype="${ftype}" RETURN n`);
    resultPromise.then(result => {
        session.close();
        let confs = result.records.map((record) => record.get(0));
        let rs = [];
        if(confs.length>0){
            let conf = confs[0];
            let data = conf['properties']['data'];
            rs = JSON.parse(data);
            // rs = data.map(x=>x.name);
        }
        callback(rs);
    });
}

const cache={};
//异步缓存函数
function getCache(name,callback,supplier,timeout=5000) {
    let now = new Date().getTime();
    if(cache[name] && (now - cache[name].lastTimestamp<=timeout)){
        callback(cache[name].value);
    }else {
        supplier(callback,cache,name);
    }
}
//同步的缓存
function getCacheSync(name,supplier,timeout=5000) {
    let now = new Date().getTime();
    if(cache[name] && (now - cache[name].lastTimestamp<=timeout)){
        return cache[name].value;
    }else {
        let val = supplier();
        cache[name]={
            lastTimestamp:now,
            value:val
        };
        return val;
    }
}

window.basePort = localStorage.getItem("serverPort");
window.basePort = window.basePort || 8082;


(function (self) {
const support = {
  searchParams: 'URLSearchParams' in self,
  iterable: 'Symbol' in self && 'iterator' in Symbol,
  blob:
    'FileReader' in self &&
    'Blob' in self &&
    (function() {
      try {
        new Blob();
        return true
      } catch (e) {
        return false
      }
    })(),
  formData: 'FormData' in self,
  arrayBuffer: 'ArrayBuffer' in self
};

function isDataView(obj) {
  return obj && DataView.prototype.isPrototypeOf(obj)
}

if (support.arrayBuffer) {
  const viewClasses = [
    '[object Int8Array]',
    '[object Uint8Array]',
    '[object Uint8ClampedArray]',
    '[object Int16Array]',
    '[object Uint16Array]',
    '[object Int32Array]',
    '[object Uint32Array]',
    '[object Float32Array]',
    '[object Float64Array]'
  ];

  var isArrayBufferView =
    ArrayBuffer.isView ||
    function(obj) {
      return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1
    }
}

class Headers {
  constructor(headers){
    this.map = {};

    if (headers instanceof Headers) {
      headers.forEach((value, name)=>{
        this.append(name, value)
      },this)
    } else if (Array.isArray(headers)) {
      headers.forEach((header)=> {
        this.append(header[0], header[1])
      }, this)
    } else if (headers) {
      Object.getOwnPropertyNames(headers).forEach((name) =>{
        this.append(name, headers[name])
      }, this)
    }
  }

  static normalizeName(name) {
    if (typeof name !== 'string') {
      name = String(name)
    }
    if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) {
      throw new TypeError('Invalid character in header field name')
    }
    return name.toLowerCase()
  }

  static normalizeValue(value) {
    if (typeof value !== 'string') {
      value = String(value)
    }
    return value
  }

  // Build a destructive iterator for the value list
  static iteratorFor(items) {
    const iterator = {
      next: function () {
        const value = items.shift();
        return {done: value === undefined, value: value}
      }
    };

    if (support.iterable) {
      iterator[Symbol.iterator] = function() {
        return iterator
      }
    }

    return iterator
  }

  append(name, value) {
    name = Headers.normalizeName(name);
    value = Headers.normalizeValue(value);
    const oldValue = this.map[name];
    this.map[name] = oldValue ? oldValue + ', ' + value : value
  }

  get(name) {
    name = Headers.normalizeName(name);
    return this.has(name) ? this.map[name] : null
  }


  has(name) {
    return this.map.hasOwnProperty(Headers.normalizeName(name))
  }

  set(name, value) {
    this.map[Headers.normalizeName(name)] = Headers.normalizeValue(value)
  };

  forEach(callback, thisArg) {
    for (var name in this.map) {
      if (this.map.hasOwnProperty(name)) {
        callback.call(thisArg, this.map[name], name, this)
      }
    }
  }

 keys() {
   const items = [];
   this.forEach(function(value, name) {
      items.push(name)
    });
    return Headers.iteratorFor(items)
  }

  values() {
    const items = [];
    this.forEach(function(value) {
      items.push(value)
    });
    return Headers.iteratorFor(items)
  }

  entries() {
    const items = [];
    this.forEach(function(value, name) {
      items.push([name, value])
    });
    return Headers.iteratorFor(items)
  }
}

Headers.prototype['delete'] = function(name) {
  delete this.map[Headers.normalizeName(name)]
};

if (support.iterable) {
  Headers.prototype[Symbol.iterator] = Headers.prototype.entries
}

function consumed(body) {
  if (body.bodyUsed) {
    return Promise.reject(new TypeError('Already read'))
  }
  body.bodyUsed = true
}

function fileReaderReady(reader) {
  return new Promise(function(resolve, reject) {
    reader.onload = function() {
      resolve(reader.result)
    }
    reader.onerror = function() {
      reject(reader.error)
    }
  })
}

function readBlobAsArrayBuffer(blob) {
  var reader = new FileReader()
  var promise = fileReaderReady(reader)
  reader.readAsArrayBuffer(blob)
  return promise
}

function readBlobAsText(blob) {
  var reader = new FileReader()
  var promise = fileReaderReady(reader)
  reader.readAsText(blob)
  return promise
}

function readArrayBufferAsText(buf) {
  var view = new Uint8Array(buf)
  var chars = new Array(view.length)

  for (var i = 0; i < view.length; i++) {
    chars[i] = String.fromCharCode(view[i])
  }
  return chars.join('')
}

function bufferClone(buf) {
  if (buf.slice) {
    return buf.slice(0)
  } else {
    var view = new Uint8Array(buf.byteLength)
    view.set(new Uint8Array(buf))
    return view.buffer
  }
}

class Body{
  constructor() {
    this.bodyUsed = false;
  }

  _initBody(body) {
    this._bodyInit = body;
    if (!body) {
      this._bodyText = ''
    } else if (typeof body === 'string') {
      this._bodyText = body
    } else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
      this._bodyBlob = body
    } else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
      this._bodyFormData = body
    } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
      this._bodyText = body.toString()
    } else if (support.arrayBuffer && support.blob && isDataView(body)) {
      this._bodyArrayBuffer = bufferClone(body.buffer);
      // IE 10-11 can't handle a DataView body.
      this._bodyInit = new Blob([this._bodyArrayBuffer])
    } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
      this._bodyArrayBuffer = bufferClone(body)
    } else {
      this._bodyText = body = Object.prototype.toString.call(body)
    }

    if (!this.headers.get('content-type')) {
      if (typeof body === 'string') {
        this.headers.set('content-type', 'text/plain;charset=UTF-8')
      } else if (this._bodyBlob && this._bodyBlob.type) {
        this.headers.set('content-type', this._bodyBlob.type)
      } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
        this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8')
      }
    }
  }

  text() {
    const rejected = consumed(this);
    if (rejected) {
      return rejected
    }

    if (this._bodyBlob) {
      return readBlobAsText(this._bodyBlob)
    } else if (this._bodyArrayBuffer) {
      return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer))
    } else if (this._bodyFormData) {
      throw new Error('could not read FormData body as text')
    } else {
      return Promise.resolve(this._bodyText)
    }
  }

  json() {
    return this.text().then(JSON.parse)
  }
}

if (support.blob) {
  Body.prototype.blob = function () {
    const rejected = consumed(this);
    if (rejected) {
      return rejected
    }

    if (this._bodyBlob) {
      return Promise.resolve(this._bodyBlob)
    } else if (this._bodyArrayBuffer) {
      return Promise.resolve(new Blob([this._bodyArrayBuffer]))
    } else if (this._bodyFormData) {
      throw new Error('could not read FormData body as blob')
    } else {
      return Promise.resolve(new Blob([this._bodyText]))
    }
  };



  Body.prototype.arrayBuffer = function () {
    if (this._bodyArrayBuffer) {
      return consumed(this) || Promise.resolve(this._bodyArrayBuffer)
    } else {
      return this.blob().then(readBlobAsArrayBuffer)
    }
  }
}

if (support.formData) {
  Body.prototype.formData = function () {
    return this.text().then(decode)
  }
}

// HTTP methods whose capitalization should be normalized
const methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'];

function normalizeMethod(method) {
  const upcased = method.toUpperCase();
  return methods.indexOf(upcased) > -1 ? upcased : method
}

class Request extends Body{
  constructor(input, options) {
    super();
    options = options || {};
    let body = options.body;

    if (input instanceof Request) {
      if (input.bodyUsed) {
        throw new TypeError('Already read')
      }
      this.url = input.url;
      this.credentials = input.credentials;
      if (!options.headers) {
        this.headers = new Headers(input.headers)
      }
      this.method = input.method;
      this.mode = input.mode;
      this.signal = input.signal;
      if (!body && input._bodyInit != null) {
        body = input._bodyInit;
        input.bodyUsed = true
      }
    } else {
      this.url = String(input)
    }

    this.credentials = options.credentials || this.credentials || 'same-origin'
    if (options.headers || !this.headers) {
      this.headers = new Headers(options.headers)
    }
    this.method = normalizeMethod(options.method || this.method || 'GET')
    this.mode = options.mode || this.mode || null;
    this.signal = options.signal || this.signal;
    this.referrer = null;

    if ((this.method === 'GET' || this.method === 'HEAD') && body) {
      throw new TypeError('Body not allowed for GET or HEAD requests')
    }
    this._initBody(body)
  }

  clone(){
    return new Request(this, {body: this._bodyInit})
  }
}


function decode(body) {
  const form = new FormData();
  body
    .trim()
    .split('&')
    .forEach(function(bytes) {
      if (bytes) {
        const split = bytes.split('=');
        const name = split.shift().replace(/\+/g, ' ');
        const value = split.join('=').replace(/\+/g, ' ');
        form.append(decodeURIComponent(name), decodeURIComponent(value))
      }
    });
  return form
}

function parseHeaders(rawHeaders) {
  const headers = new Headers();
  // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space
  // https://tools.ietf.org/html/rfc7230#section-3.2
  const preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' ');
  preProcessedHeaders.split(/\r?\n/).forEach(function(line) {
    const parts = line.split(':');
    const key = parts.shift().trim();
    if (key) {
      const value = parts.join(':').trim();
      headers.append(key, value)
    }
  });
  return headers
}

// Body.call(Request.prototype);

class Response extends Body {
  constructor(bodyInit, options) {
    super();
    if (!options) {
      options = {}
    }

    this.type = 'default';
    this.status = options.status === undefined ? 200 : options.status;
    this.ok = this.status >= 200 && this.status < 300;
    this.statusText = 'statusText' in options ? options.statusText : 'OK';
    this.headers = new Headers(options.headers);
    this.url = options.url || '';
    this._initBody(bodyInit)
  }

  clone() {
    return new Response(this._bodyInit, {
      status: this.status,
      statusText: this.statusText,
      headers: new Headers(this.headers),
      url: this.url
    })
  }

  error () {
    const response = new Response(null, {status: 0, statusText: ''});
    response.type = 'error';
    return response
  }

  redirect(url, status) {
    if (Response.redirectStatuses.indexOf(status) === -1) {
      throw new RangeError('Invalid status code')
    }
    return new Response(null, {status: status, headers: {location: url}})
  }
}

Response.redirectStatuses = [301, 302, 303, 307, 308];

// Body.call(Response.prototype)



var DOMException = self.DOMException;
try {
  new DOMException()
}
catch (err) {
  DOMException = class  {
    constructor(message, name) {
      this.message = message;
      this.name = name;
      const error = Error(message);
      this.stack = error.stack;
    }
  };
  DOMException.prototype = Object.create(Error.prototype);
  DOMException.prototype.constructor = DOMException
}

function fetch(input, init) {
  return new Promise(function(resolve, reject) {
    const request = new Request(input, init);

    if (request.signal && request.signal.aborted) {
      return reject(new DOMException('Aborted', 'AbortError'))
    }

    const xhr = new XMLHttpRequest();

    function abortXhr() {
      xhr.abort()
    }

    xhr.onload = function() {
      const options = {
        status: xhr.status,
        statusText: xhr.statusText,
        headers: parseHeaders(xhr.getAllResponseHeaders() || '')
      };
      options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL');
      const body = 'response' in xhr ? xhr.response : xhr.responseText;
      resolve(new Response(body, options))
    };

    xhr.onerror = function() {
      reject(new TypeError('Network request failed'))
    };

    xhr.ontimeout = function() {
      reject(new TypeError('Network request failed'))
    };

    xhr.onabort = function() {
      reject(new DOMException('Aborted', 'AbortError'))
    };

    xhr.open(request.method, request.url, true);

    if (request.credentials === 'include') {
      xhr.withCredentials = true
    } else if (request.credentials === 'omit') {
      xhr.withCredentials = false
    }

    if ('responseType' in xhr && support.blob) {
      xhr.responseType = 'blob'
    }

    request.headers.forEach(function(value, name) {
      xhr.setRequestHeader(name, value)
    });

    if (request.signal) {
      request.signal.addEventListener('abort', abortXhr);

      xhr.onreadystatechange = function() {
        // DONE (success or failure)
        if (xhr.readyState === 4) {
          request.signal.removeEventListener('abort', abortXhr)
        }
      }
    }

    xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
  })
}

fetch.polyfill = true;

if (!self.fetch) {
  self.fetch = fetch;
  self.Headers = Headers;
  self.Request = Request;
  self.Response = Response
}
})(window);
if(!neo4j_config){
    var neo4j_config = {
        useNeo4j:true,
        url:"bolt://"+window.location.hostname,
        user:"neo4j",
        password:"123456"
    };
}
class Neo4jService{
    constructor(){
        this.rebuild();
    }
    getSession(){
        if (neo4j_config.useNeo4j) {
            return this.driver.session();
        }
        return this.remoteSession;
    }

    closeConnection() {
        if (neo4j_config.useNeo4j) {
            this.driver.close();
        }
    };

    rebuild() {
        if (neo4j_config.useNeo4j) {
            this.driver = neo4j.v1.driver(neo4j_config.url, neo4j.v1.auth.basic(neo4j_config.user, neo4j_config.password));
        }else {
            this.remoteSession = new RemoteNeo4jServiceSession(neo4j_config);
        }
    };
}

class MyRecord {
    constructor(record) {
        this.keys = Object.keys(record);
        this.valueMap = record;
    }
    get(indexOrName){
        if (indexOrName in this.valueMap){
            return this.valueMap[indexOrName];
        }
        if (typeof indexOrName === 'number' && indexOrName%1===0 && indexOrName>=0 && indexOrName<this.keys.length ) {
            return this.valueMap[this.keys[indexOrName]];
        }
        return null;
    }
}

class RemoteNeo4jServiceSession {
    constructor(conf){
        this.config = Object.assign({},RemoteNeo4jServiceSession.default,conf);
    }
    run(cypher){
        let formData = new FormData();
        formData.append('cypher' , cypher);
        return fetch(this.config.base_url,{
            method:"POST",
            body:formData,
            // headers:{
            //     "Content-Type": "application/x-www-form-urlencoded"
            // }
        })
            .then(response=>response.json())
            .then(records=>{
                return {
                    records:records.map(r=>new MyRecord(r))
                }
            });
    }

    close(){

    }
}
RemoteNeo4jServiceSession.default = {
    base_url:'http://127.0.0.1:8080/cypher'
};

const neo4jService = new Neo4jService();
class Edge{
    constructor(g,from,to,$state='new'){
        Edge.idGenerator++;
        this.graph = g;
        this.from = from;
        this.to = to;
        this.$state = $state;
        this.$removed = false;
        this.id = Edge.idGenerator;
    }

    linkType(){
        let edgeType = "linkTo";
        switch (this.graph.getNode(this.to).$type){
            case 'Component':
            case 'ComponentGroup':
                edgeType = (this.graph.getNode(this.from).$type=='ComponentGroup') ? 'contains' : 'of';
                break;
            case 'Feature':
            case 'FeatureGroup':
                edgeType = (this.graph.getNode(this.from).$type=='FeatureGroup') ? 'contains' : 'hasFeatures';
                break;
            case 'Reason':
            case 'ReasonGroup':
                edgeType = (this.graph.getNode(this.from).$type=='ReasonGroup') ? 'contains' : 'hasReasons';
                break;
        }
        return edgeType;
    }

    persist(){
        if(this.$state=='new'){
            if(this.$removed)
                return;
            let session = neo4jService.getSession();
            let resultPromise = session.run(`start f=node(${this.from}),t=node(${this.to}) create (f)-[r:${this.linkType()}]->(t) return r `);
            resultPromise.then(result => {
                session.close();
            });
        }else //if(this.$state=='dirty')
        {
            if(this.$removed){
                let session = neo4jService.getSession();
                let resultPromise = session.run(`start f=node(${this.from}),t=node(${this.to}) match (f)-[r]->(t) delete r `);
                resultPromise.then(result => {
                    session.close();
                });
            }else{
                //do nothing
            }
            // let sets = '';
            // for(let key of Object.keys(node)){
            //     if(!/^\$\S+/g.test(key) && key!='id'){
            //         sets=`${sets} set n.${key}="${node[key]}" `
            //     }
            // }


        }
    }
}
Edge.idGenerator=0;
const node_icon={
    'Fault':["glyphicon","glyphicon-alert"],
    'Feature':["glyphicon","glyphicon-star"],
    'Reason':["glyphicon","glyphicon-star-empty"],
    'Component':["glyphicon","glyphicon-cog"],
    'FeatureGroup':["glyphicon","glyphicon-folder-open"],
    'ReasonGroup':["glyphicon","glyphicon-folder-open"],
    'ComponentGroup':["glyphicon","glyphicon-folder-open"]
};

class Graph{
    constructor(){
        this.nodes=new Map();
        this.edges=[];
        this.matchedNodes=[];
    }

    static merge(...gs){
        let g = new Graph();
        g.nodes = new Map(
            gs.map(gg=>gg.nodes.keys()).reduce((x,y)=>x.concat(y),[]),
            gs.map(gg=>gg.nodes.values()).reduce((x,y)=>x.concat(y),[])
            );
        g.edges = gs.map(gg=>gg.edges).reduce((x,y)=>x.concat(y),[]);
    }

    addNode(node){
        this.nodes.set(node.id,node);
    }
    getNode(id){
        return this.nodes.get(`${id}`);
    }

    getNodeFromList(id,list){
        return [...list].find(n=>n.id == `${id}`);
    }

    addLink(from,to,$state='new'){
        let edges = this.edges.filter(edge=>`${edge.from}`===`${from}` && `${edge.to}`===`${to}`);
        if(edges.length>0){
            edges.forEach(edge=>edge.$removed = true);
            edges[0].$removed = false;
        }
        else if (this.getNode(`${from}`)==null || this.getNode(`${to}`)==null){
            let newEdge = new Edge(this,from,to,$state);
            newEdge.$removed = true;
            this.edges.push(newEdge);
        } else
            this.edges.push(new Edge(this,from,to,$state));
    }
    getLink(id){
        for (let i=0;i<this.edges.length;i++){
            let eg = this.edges[i];
            if(`${eg.id}`==`${id}`)
                return eg;
        }
    }

    getLinkIndex(id){
        for (let i=0;i<this.edges.length;i++){
            let eg = this.edges[i];
            if(`${eg.id}`==`${id}`)
                return i;
        }
        return -1;
    }

    resetLink(edgeId,toNode,callback){
        for (let i=0;i<this.edges.length;i++){
            let eg = this.edges[i];
            if(`${eg.id}`==`${edgeId}`) {
                let neg = new Edge(this,eg.from,toNode.id);
                eg.$removed = true;
                this.addNode(toNode);
                this.edges.push(neg);
                break;
            }
        }
        if(callback != null && callback instanceof Function){
            callback(this);
        }
    }

    toJsonTree(){
        //TODO 转为 bootstrap 所需的树状结构
        const that = this;
        function toTree(nodeId,fromEdge) {
            let sub_edges = [...that.edges].filter(edge=>(!edge.$removed)&&edge.from==nodeId);
            let node = that.getNode(nodeId);
            if(sub_edges.length>0){
                return {
                    text: node.getText(),
                    referId:nodeId,
                    referEdge:fromEdge,
                    icon: node_icon[node.$type],
                    selectable: true,
                    isMatched:node.isMatched||that.matchedNodes.some(nid=>nid=node.id),
                    children:sub_edges.map(edge=>toTree(edge.to,edge.id))
                };
            }else{
                return {
                    text: node.getText(),
                    referId:nodeId,
                    referEdge:fromEdge,
                    icon: node_icon[node.$type],
                    selectable: true,
                    isMatched:node.isMatched||that.matchedNodes.some(nid=>nid=node.id),
                    children:[]
                }
            }
        }

        let nodesList = [... this.nodes.values()];
        let root = nodesList.filter(node=>node.$type=='Fault')[0];
        return toTree(root.id,null);
    }

    toJson(){
        const that = this;
        function exposeJson(node) {
            let json = node.toJson();
            let sub_edges = [...that.edges].filter(edge=>(!edge.$removed)&&edge.from==node.id);
            if(sub_edges.length>0){
                json["children"] = sub_edges.map(edge=>that.getNode(edge.to)).map(nd=>exposeJson(nd));
            }
            return json;
        }

        let fault = this.getRoot();
        return exposeJson(fault);
    }

    getRoot(){
        let nodesList = [... this.nodes.values()];
        let root = nodesList.filter(node=>node.$type=='Fault')[0];
        return root;
    }

    getReasons(){
        return _(this.edges).chain()
            .filter(edge=>!edge.$removed)
            .map(edge=>edge.to)
            .uniq()
            .map(id=>this.getNode(id))
            .filter(node=>node.$type=='Reason')
            .value()
            .sort(MyNode.compare);

        // let nodesList = [... this.nodes.values()].filter(node=>node.$type=='Reason').sort(MyNode.compare);
        // return nodesList;
    }

    getFeatures(){
        return _(this.edges).chain()
            .filter(edge=>!edge.$removed)
            .map(edge=>edge.to)
            .uniq()
            .map(id=>this.getNode(id))
            .filter(node=>node.$type=='Feature')
            .value()
            .sort(MyNode.compare);
        // let nodesList = [... this.nodes.values()].filter(node=>node.$type=='Feature').sort(MyNode.compare);
        // return nodesList;
    }

    getFeatureSize(){
        return this.getFeatures().length;
    }

    getReasonSize(){
        return this.getReasons().length;
    }

    markSubGraph(){
        const that = this;
        let session = neo4jService.getSession();
        function mark(node){
            if (node.$type == 'Feature' || (node.$type == 'FeatureGroup' && node.combineType != 'or')){
                return new Promise(function(resolve, reject){
                    node.$is_sub_graph = true;
                    resolve();
                });
            }else if (node.$type == 'Fault' || (node.$type == 'FeatureGroup' && node.combineType == 'or')){
                return MyNode.nextHop(session,node.id).then(nextHop=>{
                    return Promise.all(nextHop.map(nd=>{
                        let t_nd = that.getNode(nd.id);
                        return mark(t_nd);
                    }));
                });
            }
        }
        return mark(that.getRoot());
    }

    markSubGraph2(){
        const that = this;
        let session = neo4jService.getSession();
        function mark2(node,reasonId){
            if (node.$type == 'Feature' || (node.$type == 'FeatureGroup' && node.combineType != 'or')){
                return new Promise(function(resolve, reject){
                    node.$is_sub_graph2 = true;
                    node.$reasons.add(reasonId);
                    resolve();
                });
            }else if (node.$type == 'Fault' || node.$type == 'ReasonGroup' || node.$type == 'Reason' || (node.$type == 'FeatureGroup' && node.combineType == 'or')){
                return MyNode.nextHop(session,node.id).then(nextHop=>{
                    return Promise.all(nextHop.map(nd=>{
                        let t_nd = that.getNode(nd.id);
                        if (node.$type == 'Fault' || node.$type == 'ReasonGroup') {
                            return mark2(t_nd,null);
                        }
                        else if (node.$type == 'Reason'){
                            return mark2(t_nd,node.id);
                        } else if(reasonId!=null){
                            return mark2(t_nd,reasonId);
                        }
                        return Promise.resolve(t_nd);
                    }));
                });
            }
        }
        return mark2(that.getRoot(),null);
    }

    removeFromDB(callback){
        let root = this.getRoot();
        const fail = function (e) {
            console.error(e);
        };

        let session = neo4jService.getSession();

        session.run(`match (n)-[*]->()-[r]->(m:Component) where id(n)=${root.id} delete r `)
            .then(r1=>session.run(`match (n)-[r]->() where id(n)=${root.id} delete r delete n`))
            .then(r2=>`match (n) where n:Component or n:Configure with id(n) as x return x union match p=(n:Fault)-[*]->() with nodes(p) as pl unwind pl as xn with id(xn) as x return distinct x`)
            .then(r3=>{
                const ids = r3.records.map((record) => record.get(0));
                return session.run(`with [${ids.join(",")}] as nids match (n)-[r]-() where not id(n) in nids delete r delete n`).then(()=>ids);
            })
            .then(ids=>session.run(`with [${ids.join(",")}] as nids match (n) where not id(n) in nids delete n`))
            .then(()=>{
                session.close();
                callback && callback();
            })
            .catch(e=>{
                if(fail != null && fail instanceof Function){
                    fail(e);
                }
            });

        // Neo4jService.doExecute(session,`match (n)-[*]->()-[r]->(m:Component) where id(n)=${root.id} delete r `,(r1)=>{
        //     Neo4jService.doExecute(session,`match (n)-[r]->() where id(n)=${root.id} delete r delete n`,(r2)=>{
        //         Neo4jService.doExecute(session,`match (n) where n:Component or n:Configure with id(n) as x return x union match p=(n:Fault)-[*]->() with nodes(p) as pl unwind pl as xn with id(xn) as x return distinct x`,(r3)=>{
        //             const ids = r3.records.map((record) => record.get(0));
        //             Neo4jService.doExecute(session,`with [${ids.join(",")}] as nids match (n)-[r]-() where not id(n) in nids delete r delete n`,(r4)=>{
        //                 Neo4jService.doExecute(session,`with [${ids.join(",")}] as nids match (n) where not id(n) in nids delete n`,(r5)=>{
        //                     session.close();
        //                     callback && callback();
        //                 },fail);
        //             },fail);
        //         },fail);
        //     },fail);
        // },fail);

    }

    persistClone(success,fail){
        let nds = [...this.nodes.values()];

        nds = nds.filter(nd=>nd.$type!='Component' && nd.$state!='new').map(node=>{
            node.$state = 'new';
            return node;
        });

        this.edges = this.edges.filter(eg=>eg.$state!='new').map(eg=>{
            eg.$state = 'new';
            return eg;
        });

        let rawEntities = nds.filter(nd=>nd.$state!='new').map(node=>`n${node.id}=node(${node.id})`);
        let modifiedEntity = nds.filter(nd=>nd.$state!='new').map(node=>{
            let str = '';
            for(let key of Object.keys(node)){
                if(!/^\$\S+/g.test(key) && key!='id'){
                    let jsonStr = angular.toJson(node[key],false);
                    str = `${str} set n${node.id}.${key}=${jsonStr} `;
                }
            }
            return str;
        });

        let entities = nds.filter(nd=>nd.$state=='new').map(node=>`(n${node.id}${node.toString()}) `);

        let rawEdges = this.edges.filter(eg=>eg.$state!='new'&&eg.$removed).map(eg=>`(n${eg.from})-[r${eg.id}]->(n${eg.to})`);
        let newEdges = this.edges.filter(eg=>eg.$state=='new'&&!eg.$removed).map(eg=>`(n${eg.from})-[r${eg.id}:${eg.linkType()}]->(n${eg.to})`);
        let deleteEdges = this.edges.filter(eg=>eg.$state!='new'&&eg.$removed).map(eg=>`r${eg.id}`);

        let qstring =``;
        if(rawEntities.length>0)
            qstring = `${qstring} start ${rawEntities.join(',')}`;
        if(rawEdges.length>0)
            qstring = `${qstring} match ${rawEdges.join(',')}`;
        if(entities.length>0)
            qstring = `${qstring} create ${entities.join(',')}`;
        if(newEdges.length>0)
            qstring = `${qstring} create ${newEdges.join(',')}`;
        if(modifiedEntity.length>0)
            qstring = `${qstring} ${modifiedEntity.join(' ')}`;
        if(deleteEdges.length>0)
            qstring = `${qstring} delete ${deleteEdges.join(',')}`;

        let session = neo4jService.getSession();
        let resultPromise = session.run(qstring);
        resultPromise
            .catch(e=> {
                if(fail != null && fail instanceof Function){
                    fail(e);
                }
            })
            .then(result => {
                session.close();
                FaultNode.loadFaults();
                if(success != null && success instanceof Function){
                    success(result);
                }
            });
    }

    static loadGraph(startId,callback){
        return Graph.loadGraphPromise(startId).then(callback);
    }

    static loadGraphPromise(startId){
        return new Promise((resolve,reject)=>{
            resolve({
                g:new Graph(),
                session:neo4jService.getSession()
            });
        })
            .then((context)=>new Promise((resolve,reject)=>{
            const {g,session} = context;
            session.run(`start n=node(${startId}) MATCH (n)-[*]->(a) RETURN a UNION start a=node(${startId}) RETURN a`)
                .then(result=>{
                    result.records
                        .map((record) => record.get(0))
                        .map(node => MyNode.fromNeo4jNode(node))
                        .forEach(node=>g.nodes.set(node.id,node));
                    resolve(context);
                },fail=>{
                    resolve(context);
                });
        }))
            .then((context)=>new Promise((resolve,reject)=>{
            const {g,session} = context;
            session.run(`start n=node(${startId}) match p=(n)-[*]->(a) return relationships(p)[-1]`)
                .then(result=>{
                    result.records
                        .map((record) => record.get(0))
                        .forEach(edge=>g.addLink(edge["start"].toString(),edge["end"].toString(),'raw'));
                    resolve(context);
                });
        }))
            .then((context)=>{
                const {g,session} = context;
                session.close();
                return g;
            });
    }

    static loadAllGraph(){
        return new Promise((resolve,reject)=>{
            resolve({
                g:new Graph(),
                session:neo4jService.getSession()
            });
        }).then((context)=>new Promise((resolve,reject)=>{
                const {g,session} = context;
                session.run(`MATCH (n) where not "Configure" in labels(n)  RETURN n`)
                    .then(result=>{
                        result.records
                            .map((record) => record.get(0))
                            .map(node => MyNode.fromNeo4jNode(node))
                            .forEach(node=>{
                                if (!node || !node.id){
                                    console.info(node);
                                }else
                                    g.nodes.set(node.id,node);
                            });
                        resolve(context);
                    },fail=>{
                        resolve(context);
                    });
        })).then((context)=>new Promise((resolve,reject)=>{
            const {g,session} = context;
            session.run(`match ()-[r]->() return r`)
                .then(result=>{
                    result.records
                        .map((record) => record.get(0))
                        .forEach(edge=>g.addLink(edge["start"].toString(),edge["end"].toString(),'raw'));
                    resolve(context);
                });
        })) .then((context)=>{
            const {g,session} = context;
            session.close();
            return g;
        });
    }

    markFreedomNode(){
        const that = this;
        function mark(node,parent_type/* or--1 , and--0 */) {
            if (node instanceof FeatureNode){
                if (parent_type=="or"){
                    node.$freedom = true;
                    node.$freelist = [node.id];
                }
            } else {
                for (let edge of that.edges.filter(edge => edge.from == node.id)) {
                    let subnode = that.getNode(edge.to);
                    edge.$freedom = true;
                    mark(subnode,node.combineType);
                }
                let list = [];
                for (let edge of that.edges.filter(edge => edge.from == node.id)) {
                    let subnode = that.getNode(edge.to);
                    list.concat(subnode.$freelist || []);
                }
                node.$freelist = list;
            }
        }
        mark(this.getRoot(),1);
    }

    persist(success,fail){
        this.getRoot()["updateTimestamp"] = new Date().getTime();
        let nds = [...this.nodes.values()];
        // nds.forEach(nd=>nd.persist());
        // this.edges.forEach(eg=>eg.persist());

        let rawEntities = nds.filter(nd=>nd.$state!='new').map(node=>`n${node.id}=node(${node.id})`);
        let modifiedEntity = nds.filter(nd=>nd.$state!='new').map(node=>{
            let str = '';
            for(let key of Object.keys(node)){
                if(key=="updateTimestamp"){
                    str = `${str} set n${node.id}.${key}=${node[key]} `;
                }else
                if(!/^\$\S+/g.test(key) && key!='id' && key!='isMatched' && key != 'unknown'){
                    if (key=='matrix' && _.isObject(node[key]) && ! _.isArray(node[key])){
                            node[key]["length"] = (node[key][0]+1) * (node[key][1]+1) +2;
                            node[key] = Array.from(node[key]);
                            node[key] = _(node[key]).chain().map(x=>x instanceof Number?x:parseFloat(x)).value();
                    }

                    let jsonStr = angular.toJson(node[key],false);
                    if (key=="frequency") {
                        jsonStr = angular.toJson(jsonStr);
                    }
                    str = `${str} set n${node.id}.${key}=${jsonStr} `;
                }
            }
            return str;
        });

        let entities = nds.filter(nd=>nd.$state=='new'&&!nd.$removed).map(node=>`(n${node.id}${node.toString()}) `);

        let rawEdges = this.edges.filter(eg=>eg.$state!='new'&&eg.$removed).map(eg=>`(n${eg.from})-[r${eg.id}]->(n${eg.to})`);
        let newEdges = this.edges.filter(eg=>eg.$state=='new'&&!eg.$removed).map(eg=>`(n${eg.from})-[r${eg.id}:${eg.linkType()}]->(n${eg.to})`);
        let deleteEdges = this.edges.filter(eg=>eg.$state!='new'&&eg.$removed).map(eg=>`r${eg.id}`);

        let qstring =``;
        if(rawEntities.length>0)
            qstring = `${qstring} start ${rawEntities.join(',')}`;
        if(rawEdges.length>0)
            qstring = `${qstring} match ${rawEdges.join(',')}`;
        if(entities.length>0)
            qstring = `${qstring} create ${entities.join(',')}`;
        if(newEdges.length>0)
            qstring = `${qstring} create ${newEdges.join(',')}`;
        if(modifiedEntity.length>0)
            qstring = `${qstring} ${modifiedEntity.join(' ')}`;
        if(deleteEdges.length>0)
            qstring = `${qstring} delete ${deleteEdges.join(',')}`;

        let session = neo4jService.getSession();
        let resultPromise = session.run(qstring);
        resultPromise
            .catch(e=> {
                if(fail != null && fail instanceof Function){
                    fail(e);
                }
            })
            .then(result => {
                session.close();
                FaultNode.loadFaults();
                if(success != null && success instanceof Function){
                    success(result);
                }
        });
    }
}

class MyNode{
    constructor(type,$state='new',...obj){
        MyNode.idGenerator++;
        this.$type=type;
        this.$state=$state;
        this.id = `n${MyNode.idGenerator}`;
        this.isMatched = false;
        this.unknown = false;

        for(let o of obj){

            for(let key of Object.keys(o)){
                let v = o[key];
                if (typeof(v) == 'string'){
                    v = v.unescapeText();
                    try{
                        if(v.startsWith('[')||v.startsWith('{')||v.startsWith('"')){
                            v = JSON.parse(v.replace(/\n/g,'\\n'));
                        }
                    }catch (e){
                        console.error(e);
                        console.error(v);
                    }
                }
               this[key] = v;
            }
        }
        // jQuery.extend(true,this,...obj);
    }

    getLinks(g){
        return [... new Set(g.edges)].filter(edge => (!edge.$removed) && edge.from === this.id, this);
    }

    toString() {
        let jsonStrs = [];
        for(let key of Object.keys(this)){
            if(key=="updateTimestamp"){
                jsonStrs.push(`${key}:"${this[key]}"`);
            }else
            if(!/^\$\S+/g.test(key) && key!='id' && key!='isMatched' && key != 'unknown'){
                let v = this[key];
                v = angular.toJson(v);
                if(typeof(v) == 'string'){
                    v = v.escapeText();
                }
                jsonStrs.push(`${key}:"${v}"`);
            }
        }
        return `:${this.$type}{${jsonStrs.join(',')}}`;
    }

    toJson(){
        let json = {
            type:this.$type
        };
        for(let key of Object.keys(this)){
            if(key=="updateTimestamp"){
                json[key] = this[key];
            }else
            if(!/^\$\S+/g.test(key) && key!='id' && key!='isMatched' && key != 'unknown'){
                json[key] = this[key];
            }
        }
        return json;
    }

    static nextHop(session,current_node_id){
        return session.run(`start n=node(${current_node_id}) match (n)-[]->(m) return m`).then(result=>{
            return result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
        });
    }

    static previousHop(session,current_node_id){
        return session.run(`start n=node(${current_node_id}) match (m)-[]->(n) return m`).then(result=>{
            return result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
        });
    }

    static getAncestors(session,current_node_id){
        return session.run(`start n=node(${current_node_id}) match (m)-[*]->(n) return m`).then(result=>{
            return result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
        });
    }

    static fromNeo4jNode(neo4jNode){
        //TODO 此处将neo4j返回的节点转换为本地的类型
        let groupType='';
        let id = neo4jNode["identity"].toString();
        switch (neo4jNode['labels'][0]){
            case 'Fault':
                return new FaultNode('raw',{id: id},neo4jNode['properties']);
            case 'Feature':
                return new FeatureNode('raw',{id:id},neo4jNode['properties']);
            case 'Reason':
                return new ReasonNode('raw',{id:id},neo4jNode['properties']);
            case 'Component':
                return new ComponentNode('raw',{id:id},neo4jNode['properties']);
            case 'MatchFunction':
                return new MatchFunctionNode('raw',{id:id},neo4jNode['properties']);
            case 'FeatureGroup':
                groupType='Feature';
                break;
            case 'ReasonGroup':
                groupType='Reason';
                break;
            case 'ComponentGroup':
                groupType='Component';
                break;
            default:
                return null;
        }
        return new GroupNode(groupType,'raw',{id:id,combineType:neo4jNode['properties']['combineType']},neo4jNode['properties']);
    }

    static compare(node1,node2){
        if (node1.$type != node2.$type){
            return node1.$type > node2.$type ? 1 : -1;
        }
        if(node1.outId != node2.outId){
            return node1.outId > node2.outId ? 1 : -1;
        }

        if (node1.getText() != node2.getText()) {
            return node1.getText() > node2.getText() ? 1 : -1;
        }

        return node1.id > node2.id ? 1 : -1;
    }

    getText(){
        return `${this.getTypeName()} ${this.name}`;
    }

    getTextWithId(){
        return `[${this.id}] ${this.getText()}`;
    }

    getTemplate(edge,g){
        return this.getReadonlyTemplate(edge,g);
    }

    getReadonlyTemplate(edge,g){
        return `
<div class="form-group">
    <label class="col-sm-2 control-label">${this.getTypeName()}</label>
    <p class="col-sm-10 form-control-static">${this.name}</p>
 </div>`
    }

    persist(...callbacks){
        let session = neo4jService.getSession();
        if(this.$state=='new'){
            let resultPromise = session.run(`create (n${this.toString()}) return n `);
            resultPromise.then(result => {
                session.close();
                for (let callback of [...callbacks])
                    callback(result);
            });
        }else //if(this.$state=='dirty')
        {
            let sets = '';
            for(let key of Object.keys(this)){
                if(!/^\$\S+/g.test(key) && key!='id'){
                    let v = this[key];
                    v = angular.toJson(v);
                    if(typeof(v) == 'string'){
                        v = v.escapeText();
                    }
                    sets=`${sets} set n.${key}="${v}" `
                }
            }

            let resultPromise = session.run(`start n=node(${this.id}) ${sets} RETURN n`);
            resultPromise.then(result => {
                session.close();
                for (let callback of [...callbacks])
                    callback(result);
            });
        }
    }
}
MyNode.idGenerator = 0;

class ComponentNode extends MyNode{
    constructor($state='new',...obj){
        super('Component',$state,{
            name:"部件名称"
        },...obj);
    }

    getTypeName(){
        return "测点位置";
    }

    getTemplate(edge,g){
        // let cps = ComponentNode.components
        //     .filter(nd=>{
        //         for (let eg of $scope.graph.edges){
        //             if(eg.from==edge.from&&eg.to==nd.id)
        //                 return false;
        //         }
        //         return true;
        //     });
        // let index = g.getLinkIndex(edge.id);
        return `
 <div class="form-group">
    <label class="col-sm-2 control-label">测点位置</label>
     <div class="col-sm-10">
    <select class="form-control"
    data-ng-options="component.name for component in components"
    data-ng-model="component_ref" 
    data-ng-change="graph.resetLink(${edge},component_ref,reloadGraph)"
    data-ng-init="component_ref=graph.getNodeFromList('${this.id}',components)"
    ></select>
 </div>
 </div>
 
 <div class="form-group">
 <label class="col-sm-2 control-label">操作</label>
 <p class="col-sm-10 form-control-static">
 <a class="btn" href="javascript:;" ng-click="removeEdge(${edge})">删除当前节点</a>
</p>
</div>
`;
    }

    static loadComponents(){
        let session = neo4jService.getSession();
        let resultPromise = session.run(`MATCH (n:Component) RETURN n`);
        resultPromise.then(result => {
            session.close();
            ComponentNode.components = result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
            for (let fn of ComponentNode.loadComponents.callbacks){
                fn(ComponentNode.components);
            }
        });
    }

    static loadComponentsFrom(superId,...callbacks){
        let session = neo4jService.getSession();
        let resultPromise = session.run(`start s=node(${superId}) MATCH (s)-[*]->(n:Component) RETURN n`);
        resultPromise.then(result => {
            session.close();
            let components = result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
            for (let fn of callbacks){
                fn(components);
            }
        });
    }
}
ComponentNode.components=[];
// ComponentNode.loadComponents();
ComponentNode.loadComponents.callbacks=[];
class FaultNode extends MyNode{
    constructor($state='new',...obj){
        super('Fault',$state,{
            name:"故障名",
            outId:1,
            suggest:"正常运行"
        },...obj);
    }

    fillWithRandomValue(){
        for (let i = 0; i < (this.matrix[0]+1)*(this.matrix[1]+1); i++) {
             if (!this.matrix[i+2]){
                 this.matrix[i+2] = Math.random();
             }
        }
    }

    getTemplate(edge,g){
        getCache("fault_module",val=>{
            FaultNode.modules=val.map(x=>{
                if(x.indexOf(':')<0){
                    return {
                        category:'',
                        name:x,
                    }
                }
                let arr = x.split(':',2);
                return ({
                    category:arr[0],
                    name:arr[1],
                })
            });
        },(callback,cache,name)=>{
            getConfigure('module',data=>{
                cache[name]={
                    lastTimestamp:new Date().getTime(),
                    value:data
                };
                callback(data);
            });
        },5000);

        return `
<div class="form-group">                
    <label class="col-sm-2 control-label">故障分组</label>
    <div class="col-sm-10">
    <select class="form-control" 
    data-ng-model="graph.getNode('${this.id}').faultCategory" 
    data-ng-options="x for x in ['汽轮发电机','水轮发电机','汽轮机','锅炉','水轮机','辅机','测试']"
    data-ng-init="graph.getNode('${this.id}').faultCategory = '${this.faultCategory}'"
    ></select>
    </div>
</div>
<div class="form-group">                
    <label class="col-sm-2 control-label">故障模块</label>
    <div class="col-sm-10">
    <select class="form-control" 
    data-ng-model="graph.getNode('${this.id}').faultModule" 
    data-ng-options="x.name as x.name for x in graph.getNode('${this.id}').getModules()"
    data-ng-init="graph.getNode('${this.id}').faultModule = '${this.faultModule}'"
    ></select>
    </div>
</div>

<div class="form-group">
    <label class="col-sm-2 control-label">故障ID</label>
    <div class="col-sm-10"><input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="form-control" data-ng-model="graph.getNode('${this.id}').outId" placeholder="用于导出的ID"></div>
</div>
<div class="form-group">
    <label class="col-sm-2 control-label">故障名称</label>
    <div class="col-sm-10">
	<input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" class="form-control" data-ng-model="graph.getNode('${this.id}').name" placeholder="请输入名称">
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">故障部位</label>
    <div class="col-sm-10">
	<input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" class="form-control" data-ng-model="graph.getNode('${this.id}').location" placeholder="请输入故障部位">
    </div>
 </div>
  <div class="form-group">
    <label class="col-sm-2 control-label">运行建议</label>
    <div class="col-sm-10">
    <select class="form-control" data-ng-model="graph.getNode('${this.id}').suggest"
     data-ng-options="x for x in ['正常运行','监视运行','紧急停机']"
     data-ng-init="graph.getNode('${this.id}').suggest = '${this.suggest}'"
     ></select>
    </div>
    <label class="col-sm-2 control-label">运行描述</label>
    <div class="col-sm-10">
        <textarea data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" rows="3" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').suggest_description" placeholder="请输入建议描述"></textarea>
    </div>
     <p class="col-sm-offset-2 col-sm-10 form-control-static help-block">
     <p>说明：添加已存在节点会自动进行存储操作，并刷新整个输入区内容</p>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachFeature('${this.id}')">添加特征</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachGroup('${this.id}','FeatureGroup')">添加特征组</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachExternalFeature('${this.id}')">添加已存在特征</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachExternalGroup('${this.id}','FeatureGroup')">添加已存在特征组</a>
        <a class="button btn" data-ng-if="allowCause('${this.id}')" data-ng-click="attachReason('${this.id}')">添加原因</a>
        <a class="button btn" data-ng-if="allowCause('${this.id}')" data-ng-click="attachGroup('${this.id}','ReasonGroup')">添加原因组</a>
    </p>
 </div>
`;
    // <div class="form-group">
    //         <label class="col-sm-2 control-label">概率表&nbsp;
    // <a class="button btn" data-ng-click="graph.getNode('${this.id}').fillWithRandomValue()">填充随机概率</a>
    //         </label>
    //         <div class="col-sm-10">
    //         <table class="table table-responsive table-bordered" ng-init="fs=graph.getFeatures();rs=graph.getReasons();nf=fs.length;nr=rs.length;graph.getNode('${this.id}').matrix[0] = nf;graph.getNode('${this.id}').matrix[1] = nr;">
    //         <thead>
    //         <tr><td>特征/原因</td><td ng-repeat="reason in rs track by $index">{{reason.getText()}}</td><td>整体</td></tr>
    //     </thead>
    //     <tbody>
    //     <tr ng-repeat="feature in fs track by $index" ng-init="$index2=$index">
    //         <td>{{feature.getText()}}</td><td ng-repeat="reason in rs track by $index">
    //     <input class="form-control" data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" data-ng-model="graph.getNode('${this.id}').matrix[$index2*(nr+1)+$index+2]">
    //         </td><td> <input class="form-control" data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" data-ng-model="graph.getNode('${this.id}').matrix[($index2+1)*(nr+1)+1]"></td>
    //     </tr>
    //     </tbody>
    //     <tfoot><tr>
    //     <td>整体</td><td ng-repeat="reason in rs track by $index">
    //     <input class="form-control" data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" data-ng-model="graph.getNode('${this.id}').matrix[nf*(nr+1)+$index+2]">
    //         </td><td><input class="form-control" data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" data-ng-model="graph.getNode('${this.id}').matrix[(nf+1)*(nr+1)+1]"></td>
    //     </tr></tfoot>
    //     </table>
    //     </div>
    //     </div>
    }

    static loadFaults(){
        let session = neo4jService.getSession();
        let resultPromise = session.run(`MATCH (n:Fault) RETURN n`);
        resultPromise.then(result => {
            session.close();
            FaultNode.faults = result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
            for (let fn of FaultNode.loadFaults.callbacks){
                fn(FaultNode.faults);
            }
        });
    }

    getText(){
        let sIndex = '';
        if(this.outId!=0)
            sIndex = `<${this.outId}>`;
        return `故障${sIndex}：${this.name}`;
    }

    getTypeName(){
        return "故障";
    }

    getModules(){
        return FaultNode.modules;
    }
}
FaultNode.faults=[];
FaultNode.modules=[];
// FaultNode.loadFaults();
FaultNode.loadFaults.callbacks=[];
class FeatureNode extends MyNode{
    constructor($state='new',...obj){
        super('Feature',$state,{
            name:"特征名称",
            opertion:"特征函数",
            type:"auto",
            outId:0,
            params:[],
            $faults:new Set(),
            $reasons:new Set()
        },...obj);
        this.params.forEach((param,index)=>{
            if(param && param.low != null){
                this.params[index] = param.low;
            }
        },this);

        this.quantum = this.defaultQuantum();
    }

    isAuto(){
        return this.type=='auto';
    }

    defaultQuantum(){
        if (this.quantum instanceof Object){
            this.quantum = this.quantum.low;
        }
        return this.quantum || 100;
    }

    defaultFrequency(faultId){
        let df = {};
        df[`${faultId}`] = 0;
        return $.extend(true,df,this.frequency);
    }

    totalFrequency(){
        let dfs = this.frequency || {};
        return Object.keys(dfs).map(key=>dfs[key]).reduce((x,y)=>eval(x)+eval(y),0);
    }

    getFaults(){
        return [... this.$faults];
    }

    getReasons() {
        return [... (this.$reasons || [])]
    }

    getTemplate(edge,g){
        return `
 <div class="form-group">
    <label class="col-sm-2 control-label">导出ID</label>
    <div class="col-sm-10">
        <input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="form-control" data-ng-model="graph.getNode('${this.id}').outId" placeholder="用于导出的ID">
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">判断方式</label>
    <div class="col-sm-10">
    <select class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').type" data-ng-options="md.v as md.k for md in [{k:'机器判断',v:'auto'},{k:'人工判断',v:'manual'}]"></select>
    </div>
 </div>
 <div data-ng-show="graph.getNode('${this.id}').isAuto()" class="form-group">
    <label class="col-sm-2 control-label">特征类别</label>
    <div class="col-sm-10">
    <input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').name" placeholder="请输入名称">
    </div>
 </div>
 <div data-ng-show="graph.getNode('${this.id}').isAuto()" class="form-group">
    <label class="col-sm-2 control-label">特征函数</label>
    <div class="col-sm-10">
    <input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').opertion" placeholder="请输入函数名称">
    </div>
 </div>
 <div data-ng-show="graph.getNode('${this.id}').isAuto()" class="form-group">
    <label class="col-sm-2 control-label">固定参数列&nbsp;<a href="#" ng-click="appendArgv('${this.id}')">补充参数</a></label>
    <ol class="col-sm-10 form-control-static">
      <li ng-repeat="param in graph.getNode('${this.id}').params track by $index">
        <input class="form-control col-sm-8" ng-model-options="{updateOn: 'blur'}" ng-model="graph.getNode('${this.id}').params[$index]" placeholder="参数值">
        <a class="form-control-static col-sm-2" href="#" ng-click="removeArgv('${this.id}',$index)">删除参数</a><br>
      </li>
    </ol>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">特征描述</label>
    <div class="col-sm-10">
    <textarea data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" rows="3" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').description" placeholder="请输入特征描述"></textarea>
    </div>
 </div>
 <div class="form-group" ng-if="graph.getNode('${this.id}').$is_sub_graph">
    <label class="col-sm-2 control-label">置信度</label>
    <div class="col-sm-10">
    <table class="table table-responsive table-bordered">
    <thead>
        <tr><td>所属故障</td><td>频率</td><td>置信度</td></tr>
    </thead>
    <tbody ng-init="graph.getNode('${this.id}').quantum = graph.getNode('${this.id}').defaultQuantum(); graph.getNode('${this.id}').frequency = graph.getNode('${this.id}').defaultFrequency(graph.getRoot().id); ">
        <tr ng-repeat="ft in graph.getNode('${this.id}').getFaults()">
        <td>{{ft.getText()}}</td>
        <td>{{graph.getNode('${this.id}').frequency[ft.id] || 0}}</td>
        <td>{{((graph.getNode('${this.id}').frequency[ft.id] || 0) / graph.getNode('${this.id}').defaultQuantum() * 100).toFixed(2)}}%</td>
        </tr>
    </tbody>
    <tfoot>
        <tr><td>总和</td><td>{{graph.getNode('${this.id}').totalFrequency()}}</td>
        <td>{{(graph.getNode('${this.id}').totalFrequency() / graph.getNode('${this.id}').defaultQuantum() *100).toFixed(2)}}%</td></tr>
        <tr><td>可分配</td><td>{{graph.getNode('${this.id}').defaultQuantum() - graph.getNode('${this.id}').totalFrequency()}}</td>
        <td>{{((graph.getNode('${this.id}').defaultQuantum() - graph.getNode('${this.id}').totalFrequency()) / graph.getNode('${this.id}').defaultQuantum() * 100).toFixed(2)}}%</td></tr>
    </tfoot>
    </table>
    请输入对当前的故障分配初始频率(应小于等于可分配的数量)：
    <input  ng-change="frequencyUpdated('${this.id}')"  ng-model="graph.getNode('${this.id}').frequency[graph.getRoot().id]" data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}">
    </div>
</div>
 <br>
 <div class="form-group" ng-if="graph.getNode('${this.id}').$is_sub_graph2">
    <label class="col-sm-2 control-label">对原因的置信度</label>
    <div class="col-sm-10">
    <table class="table table-responsive table-bordered">
    <thead>
        <tr><td>所属原因</td><td>置信度</td></tr>
    </thead>
    <tbody>
        <tr ng-repeat="rid in graph.getNode('${this.id}').getReasons('${this.id}')">
        <td>{{graph.getNode(rid).getText()}}</td>
        <td ng-init="graph.getNode(rid).frequency = graph.getNode(rid).defaultFrequency();">
        <input  
          ng-model="graph.getNode(rid).frequency['${this.id}']" 
          data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}">
        </td>
        </tr>
    </tbody>
    </table>
    </div>
</div>
 <br>
 <div class="form-group">
 <label class="col-sm-2 control-label">操作</label>
 <p class="col-sm-10 form-control-static">
     <a class="btn" data-ng-if="allowComponent('${this.id}')" data-ng-click="attachComponent('${this.id}')">添加测点位置</a>
     <a class="btn" data-ng-if="allowComponent('${this.id}')" data-ng-click="attachGroup('${this.id}','ComponentGroup')">添加元件组</a>
     <a class="btn" href="javascript:;" ng-click="removeEdge(${edge})">删除当前节点</a>
 </p>
</div>
`;
    }

    getReadonlyTemplate(edge,g){
        return `
 <div class="form-group">
    <label class="col-sm-2 control-label">判断方式</label>
    <p class="col-sm-10 form-control-static">${this.type=='auto'?'机器判断':'人工判断'}</p>
 </div>
 <div data-ng-show="graph.getNode('${this.id}').isAuto()" class="form-group">
    <label class="col-sm-2 control-label">特征类别</label>
    <p class="col-sm-10 form-control-static">${this.name}</p>
 </div>
 <div data-ng-show="graph.getNode('${this.id}').isAuto()" class="form-group">
    <label class="col-sm-2 control-label">特征函数</label>
    <p class="col-sm-10 form-control-static">${this.opertion} ${this.params}</p>
 </div>
 <div data-ng-hide="graph.getNode('${this.id}').isAuto()" class="form-group">
    <label class="col-sm-2 control-label">特征描述</label>
    <p class="col-sm-10 form-control-static">${this.description}</p>
 </div>`
    }

    static loadFeatures() {
        let session = neo4jService.getSession();
        let resultPromise = session.run('MATCH (a:Feature) RETURN a');
        resultPromise.then(result => {
            session.close();
            FeatureNode.features = result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));

            FeatureNode.featureTypes = [... new Set(FeatureNode.features.filter(feature => feature.isAuto()).map(feature => feature.name))];

            //TODO Feature 下面的 Component 后续可直接使用loadGraph 中的内容
            FeatureNode.features
            // .filter(feature => feature.isAuto)
                .forEach((feature, index) => {
                    let session2 = neo4jService.getSession();
                    let resultPromise2 = session2.run(`start f=node(${feature.id}) MATCH (f)-[*]->(a:Component) RETURN a`);
                    resultPromise2.then(result => {
                        session2.close();
                        FeatureNode.features[index].components = result.records
                            .map((record) => record.get(0))
                            .map(node => node["identity"].toString());
                    });
                });

            for (let fn of FeatureNode.loadFeatures.callbacks) {
                fn(FeatureNode.features,FeatureNode.featureTypes);
            }
        });
    }

    static loadFeatures2(query,parameters={},...callbacks) {
        let session = neo4jService.getSession();
        let resultPromise = session.run(query,parameters);
        resultPromise.then(result => {
            session.close();
            let features = result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));

            let featureTypes = [... new Set(features.filter(feature => feature.isAuto()).map(feature => feature.name))];

            //TODO Feature 下面的 Component 后续可直接使用loadGraph 中的内容
            features
            // .filter(feature => feature.isAuto)
                .forEach((feature, index) => {
                    let session2 = neo4jService.getSession();
                    let resultPromise2 = session2.run(`start f=node(${feature.id}) MATCH (f)-[*]->(a:Component) RETURN a`);
                    resultPromise2.then(result => {
                        session2.close();
                        features[index].components = result.records
                            .map((record) => record.get(0))
                            .map(node => node["identity"].toString());

                        //TODO Callback Here
                        if(index+1==features.length){
                            for (let fn of callbacks) {
                                fn(features,featureTypes);
                            }
                        }
                    });
                });
        });
    }

    getText(){
        let sIndex = '';
        if(this.outId!=0)
            sIndex = `<${this.outId}>`;
        if(this.isAuto())
            return `特征${sIndex}：${this.name} ${this.opertion} ${this.params}`;
        else
            return `特征${sIndex}：${this.description}`;
    }

    getName(){
        if(this.isAuto())
            return `${this.name}`;
        else
            return `${this.description}`;
    }

    getExpression(){
        if(this.isAuto())
            return `${this.name} ${this.opertion} ${this.params}`;
        else
            return `${this.description}`;
    }
}
FeatureNode.loadFeatures.callbacks=[];
// FeatureNode.loadFeatures();

class GroupNode extends MyNode{
    constructor(entityType,$state='new',...obj){
        super(`${entityType}Group`,$state,{
            combineType:"or",
            outId:0,
            $faults:new Set(),
            $reasons:new Set()
        },...obj);

        if (entityType=="Feature"){
            this.entityType = entityType;
            this.quantum = this.defaultQuantum();
        }
    }
    getText(){
        let sIndex = '';
        if(this.outId!=0)
            sIndex = `<${this.outId}>`;
        return `${GroupNode.getMetaTypeName(this.entityType)}组合${sIndex}:${this.combineType}`;
    }

    static getMetaTypeName(entityType){
        switch (entityType) {
            case 'Feature':
                return "特征";
            case 'Reason':
                return "原因";
            case 'Component':
                return "测点";
        }
        return '';
    }

    getTypeName(){
        return "组合";
    }

    defaultQuantum(){
        if (this.quantum instanceof Object){
            this.quantum = this.quantum.low;
        }
        return this.quantum || 100;
    }

    defaultFrequency(faultId){
        let df = {};
        df[faultId] = 0;
        return $.extend(true,df,this.frequency);
    }

    totalFrequency(){
        let dfs = this.frequency || {};
        return Object.keys(dfs).map(key=>dfs[key]).reduce((x,y)=>eval(x)+eval(y),0);
    }

    getFaults(){
        return [... this.$faults];
    }

    getReasons() {
        return [... (this.$reasons || [])]
    }

    getReadonlyTemplate(edge,g){
        return `
 <div class="form-group">
    <label class="col-sm-2 control-label">组合方式</label>
    <p class="col-sm-10 form-control-static">${this.combineType}</p>
 </div>`
    }

    getTemplate(edge,g){
        return `
  <div class="form-group">
    <label class="col-sm-2 control-label">导出ID</label>
    <div class="col-sm-10">
    <input type="text" class="form-control" data-ng-model="graph.getNode('${this.id}').outId" placeholder="用于导出的ID">
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">组合方式</label>
    <div class="col-sm-10">
    <select class="form-control" data-ng-model="graph.getNode('${this.id}').combineType" data-ng-options="item for item in ['and','or','not']" placeholder="选择一项"></select>
    </div>
 </div>
  <div class="form-group" ng-if="graph.getNode('${this.id}').$is_sub_graph">
    <label class="col-sm-2 control-label">置信度</label>
    <div class="col-sm-10">
    <table class="table table-responsive table-bordered">
    <thead>
        <tr><td>所属故障</td><td>频率</td><td>概率</td></tr>
    </thead>
    <tbody ng-init="graph.getNode('${this.id}').quantum = graph.getNode('${this.id}').defaultQuantum(); graph.getNode('${this.id}').frequency = graph.getNode('${this.id}').defaultFrequency(graph.getRoot().id); ">
        <tr ng-repeat="ft in graph.getNode('${this.id}').getFaults()">
        <td>{{ft.getText()}}</td>
        <td>{{graph.getNode('${this.id}').frequency[ft.id] || 0}}</td>
        <td>{{((graph.getNode('${this.id}').frequency[ft.id] || 0) / graph.getNode('${this.id}').defaultQuantum() * 100).toFixed(2)}}%</td>
        </tr>
    </tbody>
    <tfoot>
        <tr><td>总和</td><td>{{graph.getNode('${this.id}').totalFrequency()}}</td>
        <td>{{(graph.getNode('${this.id}').totalFrequency() / graph.getNode('${this.id}').defaultQuantum() *100).toFixed(2)}}</td></tr>
        <tr><td>可分配</td><td>{{graph.getNode('${this.id}').defaultQuantum() - graph.getNode('${this.id}').totalFrequency()}}</td>
        <td>{{((graph.getNode('${this.id}').defaultQuantum() - graph.getNode('${this.id}').totalFrequency()) / graph.getNode('${this.id}').defaultQuantum() * 100).toFixed(2)}}</td></tr>
    </tfoot>
    </table>
    请输入对当前的故障分配初始频率(应小于等于可分配的数量)：
    <input ng-change="frequencyUpdated('${this.id}')" ng-model="graph.getNode('${this.id}').frequency[graph.getRoot().id]" data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}">
    </div>
  </div>
    
  <div class="form-group" ng-if="graph.getNode('${this.id}').$is_sub_graph2">
    <label class="col-sm-2 control-label">对原因的置信度</label>
    <div class="col-sm-10">
    <table class="table table-responsive table-bordered">
    <thead>
        <tr><td>所属原因</td><td>置信度</td></tr>
    </thead>
    <tbody>
        <tr ng-repeat="rid in graph.getNode('${this.id}').getReasons('${this.id}')">
        <td>{{graph.getNode(rid).getText()}}</td>
        <td ng-init="graph.getNode(rid).frequency = graph.getNode(rid).defaultFrequency();">
        <input  
          ng-model="graph.getNode(rid).frequency['${this.id}']" 
          data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}">
        </td>
        </tr>
    </tbody>
    </table>
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">备注信息</label>
    <div class="col-sm-10">
    <textarea rows="3" class="form-control" data-ng-model="graph.getNode('${this.id}').description" placeholder="请输入备注信息"></textarea>
    </div>
    <p class="col-sm-offset-2 col-sm-10 form-control-static help-block">
        <a class="btn" href="javascript:;" ng-click="removeEdge(${edge})">删除当前节点</a></label>
        <a class="btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachFeature('${this.id}')">添加特征</a>
        <a class="btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachGroup('${this.id}','FeatureGroup')">添加特征组</a>
         <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachExternalFeature('${this.id}')">添加已存在特征</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachExternalGroup('${this.id}','FeatureGroup')">添加已存在特征组</a>
        <a class="btn" data-ng-if="allowCause('${this.id}')" data-ng-click="attachReason('${this.id}')">添加原因</a>
        <a class="btn" data-ng-if="allowCause('${this.id}')" data-ng-click="attachGroup('${this.id}','ReasonGroup')">添加原因组</a>
        <a class="btn" data-ng-if="allowComponent('${this.id}')" data-ng-click="attachComponent('${this.id}')">添加元件</a>
        <a class="btn" data-ng-if="allowComponent('${this.id}')" data-ng-click="attachGroup('${this.id}','ComponentGroup')">添加元件组</a>
    </p>
 </div>`;
    }
}

class MatchFunctionNode extends MyNode{
    constructor($state='new',...obj){
        super('MatchFunction',$state,{
            name:"函数名",
            body: `
function (a,params){
    return false;
}`
        },...obj);
    }

    static loadFunctions(){
        let session = neo4jService.getSession();
        let resultPromise = session.run(`MATCH (n:MatchFunction) RETURN n`);
        resultPromise.then(result => {
            session.close();
            MatchFunctionNode.functions = result.records
                .map((record) => record.get(0))
                .map(node => MyNode.fromNeo4jNode(node));
            for (let fn of MatchFunctionNode.loadFunctions.callbacks){
                fn(MatchFunctionNode.functions);
            }
        });
    }
}
MatchFunctionNode.functions=[];
// MatchFunctionNode.loadFunctions();
MatchFunctionNode.loadFunctions.callbacks=[];

const matchFactory = function (opertion) {
    switch (opertion){
        case '>':
            return (a,params)=>Number(a) > Number(params[0]);
        case '<':
            return (a,params)=>Number(a) < Number(params[0]);
        case '>=':
            return (a,params)=>Number(a) >= Number(params[0]);
        case '<=':
            return (a,params)=>Number(a) <= Number(params[0]);
        case '=':
            return (a,params)=>a == params[0];
        case 'between':
            return (a,params)=>Number(a) >= Number(params[0]) && Number(a) <= Number(params[1]);
        case 'in':
            return (a,params)=>{
                for (let param of params){
                    if(param==a){
                        return true;
                    }
                }
                return false;
            };
        default:
    }

    for (let func of MatchFunctionNode.functions){
        if(func.name==opertion){
            return eval(`(${func.body})`);
        }
    }
    return (a,params)=>false;
};
class ReasonNode extends MyNode{
    constructor($state='new',...obj){
        super('Reason',$state,{
            name:"原因描述",
            probability:0.2,
            frequency:{},
            solution:'解决方案',
            outId:0
        },...obj);
    }

    getText(){
        let sIndex = '';
        if(this.outId!=0)
            sIndex = `<${this.outId}>`;
        return `原因${sIndex}：${this.name}`;
    }

    defaultFrequency(featureId){
        this.frequency = this.frequency || {};
        if (!this.frequency[`${featureId}`]){
            this.frequency[`${featureId}`] = 0
        }
        return this.frequency;
    }

    getTemplate(edge,g){
        return `
 <div class="form-group">
    <label class="col-sm-2 control-label">导出ID</label>
    <div class="col-sm-10">
    <input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="form-control" data-ng-model="graph.getNode('${this.id}').outId" placeholder="用于导出的ID">
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">原因描述</label>
    <div class="col-sm-10">
    <textarea data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" rows="3" class="form-control" data-ng-model="graph.getNode('${this.id}').name" placeholder="请输入原因描述"></textarea>
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">概率</label>
    <div class="col-sm-10">
    <input data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').probability" placeholder="请输入概率">
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">解决方案</label>
    <div class="col-sm-10">
    <textarea data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" rows="3" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').solution" placeholder="请输入解决方案"></textarea>
    </div>
 </div>
  <div class="form-group">
    <label class="col-sm-2 control-label">所需工具</label>
    <div class="col-sm-10">
    <textarea data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" type="text" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').tools" placeholder="请输入所需工具"></textarea>
    </div>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">备品备件</label>
    <div class="col-sm-10">
    <textarea data-ng-model-options="{updateOn:'default blur',debounce:{default:5000,blur:0}}" rows="3" class="col-sm-10 form-control" data-ng-model="graph.getNode('${this.id}').parts" placeholder="请输入备品备件"></textarea>
    </div>
 </div>
  <div class="form-group">
 <label class="col-sm-2 control-label">操作</label>
 <p class="col-sm-10 form-control-static">
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachFeature('${this.id}')">添加特征</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachGroup('${this.id}','FeatureGroup')">添加特征组</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachExternalFeature('${this.id}')">添加已存在特征</a>
        <a class="button btn" data-ng-if="allowFeature('${this.id}')" data-ng-click="attachExternalGroup('${this.id}','FeatureGroup')">添加已存在特征组</a>
     <a class="btn" data-ng-if="allowComponent('${this.id}')" data-ng-click="attachComponent('${this.id}')">添加元件</a>
     <a class="btn" data-ng-if="allowComponent('${this.id}')" data-ng-click="attachGroup('${this.id}','ComponentGroup')">添加元件组</a>
     &nbsp;<a class="btn" href="javascript:;" ng-click="removeEdge(${edge})">删除当前节点</a>
 </p>
</div>
`;
    }

    getReadonlyTemplate(edge,g){
        return `
 <div class="form-group">
    <label class="col-sm-2 control-label">原因描述</label>
    <p class="col-sm-10 form-control-static">${this.name}</p>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">概率</label>
    <p class="col-sm-10 form-control-static">${this.probability}</p>
 </div>
 <div class="form-group">
    <label class="col-sm-2 control-label">解决方案</label>
    <p class="col-sm-10 form-control-static">${this.solution}</p>
 </div>
<div class="form-group">
    <label class="col-sm-2 control-label">所需工具</label>
    <p class="col-sm-10 form-control-static">${this.tools}</p>
 </div>
<div class="form-group">
    <label class="col-sm-2 control-label">备品备件</label>
    <p class="col-sm-10 form-control-static">${this.parts}</p>
 </div>`
    }
}
